﻿using CityGrid.Common.Utilities;
using CQRSFramework.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace CQRSFramework.Domains.Impl
{
    public class AggregateRootInternalEventHandlerProvider : IAggregateRootInternalEventHandlerProvider
    {
        private readonly Dictionary<Type, Dictionary<Type, Action<IAggregateRoot, IDomainEvent>>> aggregateRootInternalEventHandlerDic = new Dictionary<Type, Dictionary<Type, Action<IAggregateRoot, IDomainEvent>>>();
        public AggregateRootInternalEventHandlerProvider(Assembly[] assemblies)
        {
            foreach (var assembly in assemblies)
            {
                foreach (var aggregateRootType in assembly.GetExportedTypes().Where(t => t.IsClass && typeof(IAggregateRoot).IsAssignableFrom(t)))
                {
                    var entries = from method in aggregateRootType.GetMethods()
                                  let parameters = method.GetParameters()
                                  where method.Name == "Handle" && parameters.Length == 1 && typeof(IDomainEvent).IsAssignableFrom(parameters.Single().ParameterType)
                                  select new { Method = method, EventType = parameters.Single().ParameterType };
                    foreach (var entry in entries)
                    {
                        RegisterInternalEventHandler(aggregateRootType, entry.EventType, entry.Method);
                    }
                }
            }
        }
        private void RegisterInternalEventHandler(Type aggregateRootType, Type eventType, MethodInfo method)
        {
            Dictionary<Type, Action<IAggregateRoot, IDomainEvent>> eventHandlerDic;
            if (!aggregateRootInternalEventHandlerDic.TryGetValue(aggregateRootType, out eventHandlerDic))
            {
                eventHandlerDic = new Dictionary<Type, Action<IAggregateRoot, IDomainEvent>>();
                aggregateRootInternalEventHandlerDic.Add(aggregateRootType, eventHandlerDic);
            }
            if (eventHandlerDic.ContainsKey(eventType))
            {
                throw new Exception(string.Format("Found duplicated event handler on aggregate, aggregate type:{0}, event type:{1}", aggregateRootType.FullName, eventType.FullName));
            }
            eventHandlerDic.Add(eventType, DelegateFactory.CompileMethodInvocation<Action<IAggregateRoot, IDomainEvent>>(aggregateRootType, method));
        }
        public Action<IAggregateRoot, IDomainEvent> GetInternalEventHandler(Type aggregateRootType, Type domainEventType)
        {
            Dictionary<Type, Action<IAggregateRoot, IDomainEvent>> eventHandlerDictionary;
            if (!aggregateRootInternalEventHandlerDic.TryGetValue(aggregateRootType, out eventHandlerDictionary))
            {
                return null;
            }
            Action<IAggregateRoot, IDomainEvent> eventHandler;
            return eventHandlerDictionary.TryGetValue(domainEventType, out eventHandler) == true ? eventHandler : null;
        }
    }
}
